package Basic;

import java.util.ArrayList;
import java.util.List;

import org.rtscup.core.Unit;

public class SecurityTiles extends Tiles {

	private int tileWidth;
	private int tileHeight;

	public int[][] tiles;
	private int attackRangeInTiles;
	private int attackRange;
	private List<Long> oldEnemiesPositions;
	private List<Long> oldAlliesPositions;

	public static int POSITIVE_INFLUENCE = 1;
	public static int NEGATIVE_INFLUENCE = -1;

	public SecurityTiles(int mapWidth, int mapHeight, Unit agent, Long[] obstacles) {
		super(mapWidth, mapHeight, mapWidth / agent.getWidth(), mapHeight / agent.getHeight(), obstacles);

		this.tileWidth = agent.getWidth();
		this.tileHeight = agent.getHeight();

		this.tiles = new int[noTilesW][noTilesH];
		this.attackRange = agent.getAttackRange();
		this.attackRangeInTiles = this.attackRange / tileWidth;

		this.oldEnemiesPositions = new ArrayList<Long>();
		this.oldAlliesPositions = new ArrayList<Long>();
	}

	/**
	 * Configures the matrix of the tiles, creating regions of positive and
	 * negative influence in it. These regions are extracted from the positions
	 * of allies and enemies units and according the its attack range.
	 * 
	 * @param allies
	 * @param enemies
	 */
	public void configureTiles(List<Unit> allies, List<Unit> enemies) {

		for (long position : oldEnemiesPositions) {
			int posX = (int) (position >> 32);
			int posY = (int) position;
			configure(posX, posY, SecurityTiles.POSITIVE_INFLUENCE);
		}

		for (long position : oldAlliesPositions) {
			int posX = (int) (position >> 32);
			int posY = (int) position;
			configure(posX, posY, SecurityTiles.NEGATIVE_INFLUENCE);
		}

		oldAlliesPositions.clear();
		oldEnemiesPositions.clear();

		for (Unit unit : enemies) {
			if (unit.isAlive()) {
				configure(unit.getX(), unit.getY(), SecurityTiles.NEGATIVE_INFLUENCE);
				storePosition(unit, oldEnemiesPositions);
			}
		}

		for (Unit unit : allies) {
			if (unit.isAlive()) {
				configure(unit.getX(), unit.getY(), SecurityTiles.POSITIVE_INFLUENCE);
				storePosition(unit, oldAlliesPositions);
			}
		}
	}

	/**
	 * Readjusts the values of matrix of tiles around a given point. The adjust
	 * can have a negative or positive influence, subtracting or adding these
	 * values according to the distance of the given position.
	 * 
	 * @param posX
	 * @param posY
	 * @param influence
	 */
	private void configure(int posX, int posY, int influence) {
		for (int j = -attackRangeInTiles; j <= attackRangeInTiles; j++) {
			for (int k = -attackRangeInTiles; k <= attackRangeInTiles; k++) {
				int x = posX + (j * this.attackRange);
				int y = posY + (k * this.attackRange);
				if (distance(posX, posY, x, y) <= attackRange && isPositionAvailable(x, y)) {
					int value = (influence * ((attackRangeInTiles + 1) + (Math.abs(j) + Math.abs(k)))) + getByPosition(x, y);
					setByPosition(x, y, value);
				}
			}
		}
	}

	/**
	 * Gets the value from the tiles matrix given some position of the map.
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public int getByPosition(int x, int y) {
		return tiles[x / tileWidth][y / tileHeight];
	}

	/**
	 * Sets a value in the tiles matrix given some position of the map.
	 * 
	 * @param x
	 * @param y
	 * @param value
	 */
	public void setByPosition(int x, int y, int value) {
		tiles[x / tileWidth][y / tileHeight] = value;
	}

	/**
	 * Verifies if a given position is available in the tiles matrix.
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	private boolean isPositionAvailable(int x, int y) {
		boolean ret;

		ret = x >= 0 && x < noTilesW && y >= 0 && y < noTilesH;

		return ret;
	}

	/**
	 * Calculates the City-Block distance between two points.
	 * 
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	private int distance(int x1, int y1, int x2, int y2) {
		int ret;

		ret = Math.abs(x1 - x2) + Math.abs(y1 - y2);

		return ret;
	}

	/**
	 * Stores the position of an unit in a given list of positions.
	 * 
	 * @param unit
	 * @return
	 */
	private void storePosition(Unit unit, List<Long> positions) {
		long position = unit.getX();
		position = (position << 32) + unit.getY();
		positions.add(position);
	}

}